﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.EntityClient;
using System.Data.Objects;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Transactions;
using AutoMapper;
using AzureDiagMon.Common;
using AzureDiagMon.Common.Algorithms.FragmentedRanges;
using AzureDiagMon.Common.Dtos;
using AzureDiagMon.Common.Helpers.Azure;
using AzureDiagMon.Common.Interfaces.Services;
using AzureDiagMon.Common.Services;
using AzureDiagMon.Model;
using Ninject;

namespace AzureDiagMon.Services
{
    public partial class DataStoreService : Service, IDataStoreService
    {
        #region Imports

        #endregion

        #region Events

        public event EventHandler<EventArgs> LogEntriesChanged;

        public void OnLogEntriesChanged(EventArgs e)
        {
            EventHandler<EventArgs> handler = LogEntriesChanged;
            if (handler != null) handler(this, e);
        }

        public event EventHandler<EventArgs> WadLogDownloadedSectionsChanged;

        public void OnWadLogDownloadedSectionsChanged(EventArgs e)
        {
            EventHandler<EventArgs> handler = WadLogDownloadedSectionsChanged;
            if (handler != null) handler(this, e);
        }

        #endregion

        #region Implementation of IDataStoreService

        public void AddSubscription(SubscriptionDto subscriptionDto)
        {
            using (var dtx = IoC.Get<IModelsContainer>())
            {
                var subscription = Mapper.Map<SubscriptionDto, Subscription>(subscriptionDto);
                dtx.SubscriptionSet.AddObject(subscription);
                dtx.SaveChanges();
            }
        }

        public IEnumerable<StorageAccountDto> ListStorageAccounts()
        {
            using (var dtx = IoC.Get<IModelsContainer>())
            {
                return dtx.StorageAccountSet.OrderBy(f => f.AccountName)
                    .ToList()
                    .Select(Mapper.Map<StorageAccount, StorageAccountDto>)
                    .ToList();
            }
        }

        public void AddStorageAccount(StorageAccountDto storageAccountDto)
        {
            using (var dtx = IoC.Get<IModelsContainer>())
            {
                var storageAccount = Mapper.Map<StorageAccountDto, StorageAccount>(storageAccountDto);
                dtx.StorageAccountSet.AddObject(storageAccount);
                dtx.SaveChanges();
            }
        }


        public void AddWadLogDownloadedSection(WadLogDownloadedSectionDto downloadedSectionDto)
        {
            using (var dtx = IoC.Get<IModelsContainer>())
            {
                var downloadedSection = Mapper.Map<WadLogDownloadedSectionDto, WadLogDownloadedSection>(downloadedSectionDto);
                dtx.WadLogDownloadedSectionSet.AddObject(downloadedSection);
                dtx.SaveChanges();
            }

            OnWadLogDownloadedSectionsChanged(EventArgs.Empty);
        }

        public IEnumerable<WadLogDownloadedSectionDto> ListWadLogDownloadedSections(int storageAccountId, DateTime? from, DateTime? to)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<WadLogDownloadedSectionDto> ListWadLogDownloadedSections(StorageAccountDto storageAccountDto, DateTime? from, DateTime? to)
        {
            if (storageAccountDto == null)
                throw new ArgumentNullException("storageAccountDto");

            using (var dtx = IoC.Get<IModelsContainer>())
            {
                long? fromKey = null;
                long? toKey = null;

                if (from.HasValue)
                    fromKey = from.Value.ToWadLogPartitionKey();
                if (to.HasValue)
                    toKey = to.Value.ToWadLogPartitionKey();

                return dtx.WadLogDownloadedSectionSet.Where(f => f.StorageAccountId == storageAccountDto.Id
                    && ((fromKey.HasValue ? true : f.StartKey >= fromKey && toKey.HasValue ? true : f.EndKey >= toKey)
                      || (fromKey.HasValue ? true : f.StartKey < fromKey && f.EndKey > fromKey && toKey.HasValue ? true : f.EndKey <= toKey)
                      || (fromKey.HasValue ? true : f.StartKey > fromKey && toKey.HasValue ? true : f.StartKey < toKey && f.EndKey > toKey)))
                                                     .ToList()
                                                     .Select(Mapper.Map<WadLogDownloadedSection, WadLogDownloadedSectionDto>)
                                                     .ToList();
            }
        }

        public WadLogDownloadedSectionsInfo GetWadLogDownloadedSectionsInfo(StorageAccountDto storageAccountDto, DateTime start, DateTime end)
        {
            using (var dtx = IoC.Get<IModelsContainer>())
            {
                var items = dtx.WadLogDownloadedSectionSet.Where(f => f.StorageAccountId == storageAccountDto.Id);
                var sections = items.ToList()
                    .Select(Mapper.Map<WadLogDownloadedSection, LoadedRangeSection<long>>)
                    .OrderBy(f => f.StartIndex)
                    .ToList();

                var fragmentedRange = new FragmentedRange<long>(start.ToWadLogPartitionKey(), end.ToWadLogPartitionKey(),
                                                                sections, new LastFirstResolverStrategy());

                var empty = fragmentedRange.GetEmptySections();
                var result = new WadLogDownloadedSectionsInfo()
                                 {
                                     HasMissingSections = empty.Count() != 0,
                                     IsFullyDownloaded = empty.Count() == 0,
                                     LastEntryDate = sections.Count > 0
                                           ? Math.Min(sections.Last().EndIndex, end.ToWadLogPartitionKey()).ToLocalDateTime()
                                           : (DateTime?)null,
                                     LastSectionMissingOnly =
                                         empty.Count() == 1 &&
                                         (long)(empty.First().EndIndex) == end.ToWadLogPartitionKey()
                                 };

                return result;
            }
        }

        public void ConsolidateWadLogDownloadedSections(StorageAccountDto storageAccountDto)
        {
            if (storageAccountDto == null)
                throw new ArgumentNullException("storageAccountDto");

#if DEBUG
            var stopwatch = Stopwatch.StartNew();
#endif
            using (var dtx = IoC.Get<IModelsContainer>())
            {
                var items = dtx.WadLogDownloadedSectionSet.Where(f => f.StorageAccountId == storageAccountDto.Id);
                var sections = items.ToList()
                                    .Select(Mapper.Map<WadLogDownloadedSection, LoadedRangeSection<long>>)
                                    .OrderBy(f => f.StartIndex)
                                    .ToList();

                if (sections.Count == 0)
                    return;

                var fragmentedRange = new FragmentedRange<long>(sections.Min(f => f.StartIndex),
                                                sections.Max(f => f.EndIndex),
                                                sections,
                                                new LastFirstResolverStrategy());

                var consolidatedSections = fragmentedRange.GetConsolidatedSections().Cast<FragmentedRangeSection<long>>();

                //TODO: Use transaction here
                foreach (var section in items)
                {
                    dtx.WadLogDownloadedSectionSet.DeleteObject(section);
                }

                foreach (var section in consolidatedSections.Select(Mapper.Map<FragmentedRangeSection<long>, WadLogDownloadedSection>))
                {
                    section.StorageAccountId = storageAccountDto.Id;
                    section.DownloadTime = DateTime.Now;
                    dtx.WadLogDownloadedSectionSet.AddObject(section);
                }

                dtx.SaveChanges();
            }
#if DEBUG
            stopwatch.Stop();
#endif
            Debug.WriteLine("Consolidation done in {0} ms", stopwatch.ElapsedMilliseconds);
            OnWadLogDownloadedSectionsChanged(EventArgs.Empty);
        }
    }
        #endregion
}
